package modelo.maestro;

import java.io.FileReader;
import java.rmi.RemoteException;
import java.util.HashMap;
import java.util.Properties;

import java.util.Map.Entry;
import java.util.logging.*;

import bbdd.configuracion.Configuracion;

import util.AgregadorRMI;
import vista.GLOBEXView;
import controlador.buzones.Buzon;
import controlador.buzones.BuzonIface;
import controlador.buzones.Mensaje;
import controlador.buzones.MessageTypes;


/**
 * Esta clase implementa el comportamiento del aut&oacute;mata maestro.
 * 
 * @author Globex Inc.
 *
 */
public class Maestro implements Runnable {
	/**
	 * Buz&oacute;n para la comunicaci&oacute;n del aut&oacute;mata eslcavo 1 al mestro.
	 */
	private BuzonIface _buzon1;
	
	/**
	 * Buz&oacute;n para la comunicaci&oacute;n del aut&oacute;mata eslcavo 2 al mestro.
	 */
	private BuzonIface _buzon2;
	
	/**
	 * Buz&oacute;n para la comunicaci&oacute;n del aut&oacute;mata eslcavo 3 al mestro.
	 */
	private BuzonIface _buzon3;

	/**
	 * Buzones para comunicaciones del maestro a los aut&oacute;matas.
	 * Las claves del HashMap son: esclavo1, esclavo2, esclavo3
	 */
	private HashMap<String, BuzonIface> _slaves;

	/**
	 * Instancia del la intefaz para el aut&oacute;mata maestro pueda comunicarse con ella.
	 */
	private static GLOBEXView _scada;

	/**
	 * Indica si el aut&oacute;mata debe continuar encendido.
	 * Al crear el automata esta a true.
	 */
	private boolean _on;

	/**
	 * Indica si el aut&oacute;mata 1 esta levantado (true) o no (false). 
	 */
	private boolean _up1;

	/**
	 * Indica si el aut&oacute;mata 2 esta levantado (true) o no (false). 
	 */
	private boolean _up2;

	/**
	 * Indica si el aut&oacute;mata 3 esta levantado (true) o no (false). 
	 */
	private boolean _up3;

	/**
	 * Puerto donde el aut&oacute;mata publica en buz&oacute;n.
	 */
	private int _port;

	/**
	 * Ruta del buz&oacute;n del aut&oacute;mata. 
	 */
	private String _id;

	/**
	 * Instancia de la clase que modela el comportamiento del robot 2.
	 */
	private Robot2 _robot2;

	/**
	 * El ciclo de reloj. 
	 */
	private long _reloj;

	/**
	 * La configuraci&oacute;n actual del aut&oacute;mata. 
	 */
	private HashMap<String, String> _conf;

	/**
	 * Estado del aut&oacute;mata 1
	 */
	private HashMap<String, String> _estado1 = new HashMap<String, String>();

	/**
	 * Estado del aut&oacute;mata 2
	 */
	private HashMap<String, String> _estado2 = new HashMap<String, String>();

	/**
	 * Estado del aut&oacute;mata 3 
	 */
	private HashMap<String, String> _estado3 = new HashMap<String, String>();

	/* se pueden dejar cosas en transferncia */
	//private boolean _transferenciaIn = true;
	/**
	 * Indica si hay elemento disponibles en la cinta de transferencia.
	 */
	private boolean _transferenciaOut = false;

	/**
	 * Indica si se pueden dejar elementos en la estaci&oacute;n de soldadura. 
	 */
	private boolean _soldaduraIn = true;
	
	/**
	 * Indica si se pueden retirar elementos de la estaci&oacute;n de soldadura. 
	 */
	private boolean _soldaduraOut = false;

	/**
	 * Indica si se pueden dejar elementos en la estaci&oacute;n de control. 
	 */
	private boolean _controlIn = true;
	
	/**
	 * Indica si se pueden retirar elementos de la estaci&oacute;n de control. 
	 */
	private boolean _controlOut = false;

	/**
	 * Los que hay esperando en control es aceptado
	 */
	private boolean _aceptado = false;

	/**
	 * La &uacute;ltima orden enviada al robot2
	 */
	private OrdenesRobot2 _orden_anterior;
	
	/**
	 * La &uacute;ltima posici&oacute;n del robot 2
	 */
	private PosicionesRobot2 _pos_anterior;

	/**
	 * El estado actual de todo el sistema. Puede ser START, STOP o EMERGENCY_STOP.
	 */
	private static MessageTypes _parada;
	
	/**
	 * Indica si se ha notificado del cambio de estado a todos los aut&oacute;matas.
	 */
	private static boolean _paradaNotified;

	/**
	 * Usado para los mensajes de log del automata maestro.
	 */
	private static Logger _logger = Logger.getLogger("automata.maestro");

	/**
	 * Constructor del aut&oacute;mata maestro.
	 *  
	 * @param iface Interfaz a la que est&aacute; conectado. Si es null indica que se ejecuta sin interfaz.
	 * @throws RemoteException
	 */
	public Maestro(GLOBEXView iface) throws RemoteException {
		_scada = iface;
		
		try {
			//LogManager.getLogManager().readConfiguration(new FileInputStream("logging.properties"));
			Properties myConf = new Properties();
			myConf.load(new FileReader("maestro.conf"));
			_port = Integer.parseInt(myConf.getProperty("localPort"));
			_id = myConf.getProperty("localID");
		} catch (Exception e) {
			_logger.log(Level.SEVERE,"" , e);
			System.exit(-1);
		}
		
		if(iface == null) {
			try {
				_logger.fine("La referencia a la interfaz es null");
				Properties p = new Properties();
				p.load(new FileReader("configuracion.bbdd"));
				
				// TODO
				/*Set<String> keys = (Set<String>) p.keySet();
				for(String key : (Set<String>) p.keySet())
					_conf.put(key, p.getProperty(key));*/
			} catch (Exception e) {
				_logger.log(Level.SEVERE,"" , e.getStackTrace());
				System.exit(-1);
			}
		} else {
			_conf = Configuracion.getHashMap();
		}
		
		if(_logger.isLoggable(Level.FINER)) {
			StringBuffer pairs = new StringBuffer();
			for(Entry<String, String> entryes : _conf.entrySet()) {
				pairs.append("\n\t"+entryes.getKey()+" = "+entryes.getValue());
			}
			_logger.finer("Valores de configuracion\n:"+pairs);
		}

		_buzon1 = new Buzon();
		_buzon2 = new Buzon();
		_buzon3 = new Buzon();

		_reloj = Configuracion.getCicloReloj(); //Integer.parseInt(_conf.getProperty("cicloReloj"));
		_robot2 = new Robot2((int)_reloj, Integer.parseInt(_conf.get("tiempoRecMontado")),
				Integer.parseInt(_conf.get("tiempoTransMontado")),
				Integer.parseInt(_conf.get("tiempoTransSoldado")));
		_slaves = new HashMap<String, BuzonIface>();

		_on = false;

		_up1 = false;
		_up2 = false;
		_up3 = false;

		_orden_anterior = OrdenesRobot2.QUIETO;

		_parada = MessageTypes.STOP;
		_paradaNotified = true;
	}


	/**
	 * Se encarga de procesar los mensajes recibidos del aut&oacute;mata 1 y de ejecutar las acciones pertinentes.
	 * @return true si hab&iacute;a un mensaje y se ha podido procesar correctamente. False en caso contrario.
	 */
	private boolean procesarMensajes1() {
		boolean ans = false;
		Mensaje msj;
		try {
			msj = _buzon1.desencolar();
		} catch (Exception e) {
			_logger.log(Level.SEVERE, "Error al desencolar buzon1", e);
			return ans;
		}

		if(msj != null) {
			_logger.info(msj.get_source()+ " sent " + msj.get_type());

			switch(msj.get_type()) {
				case BEFRIEND:
					this.befriend(msj.get_message());

					if(_logger.isLoggable(Level.FINER)) {
						String boxes = "";
						for(String str : _slaves.keySet())
							boxes+= "\t"+str;
						_logger.finer("Avaliable mailboxes are:"+boxes);
					}

					// se ha caido
					// enviamos senal de inicio con parametros
					if(_on) {
						Mensaje start = new Mensaje();
						start.set_type(MessageTypes.START);
						start.set_message(_conf);
						try {
							_slaves.get("esclavo1").encolar(start);
						} catch (Exception e) {
							_logger.log(Level.SEVERE,"Esclavo1 is down but it shoudn't be", e);
						}
					}
					break;

				case UNFRIEND: // esto no sera usado
					_slaves.remove(msj.get_source());
					_up1 = false;
					break;

				case ESTADOS1:
					HashMap<String, String> estadoTmp = msj.get_message();
					if(!_estado1.equals(estadoTmp)) {
						_estado1 = estadoTmp;
						// mandarselo al scada
						if(_scada != null)
							_scada.modificarEstadoAutomata1(_estado1);
						_logger.info("Estado1 updated");
					}
					break;

				case CONJUNTO_MONTADO_DISPONIBLE:
					// esclavo1 -> master
					// master -> preguntar a automata 2 si ha espacio en cinta de transprefencia?
					//           no hace falta porque no se llenan
					// si hay avisamos a robot1 para que lo deje

					// avisar a scada
					if(_scada != null)
						_scada.modificarEstacionMontaje(MessageTypes.CONJUNTO_MONTADO_DISPONIBLE);

					// eviarselo a automata2
					Mensaje nuevoMontado = new Mensaje();
					nuevoMontado.set_type(MessageTypes.NUEVO_CONJUNTO_MONTADO_DISPONIBLE_CINTA_TRANSFERENCIA);

					try {
						_slaves.get("esclavo2").encolar(nuevoMontado);
						_logger.fine("E1 -> E2: nuevo conjunto montado");
					} catch (Exception e2) {
						_logger.log(Level.WARNING, "Esclavo2 is down", e2);
						this.automataCaido(2);
					}
					break;
					
				case COMIENZO_ROBOT1_TRASLADO_EJE_ENGRANAJE:
					// el robot1 comienza a transportar el eje y engranaje
					// avisamos a scada
					if(_scada != null)
						_scada.modificarRobot1(MessageTypes.COMIENZO_ROBOT1_TRASLADO_EJE_ENGRANAJE);
					break;
					
				case FIN_ROBOT1_TRASLADO_EJE_ENGRANAJE:
					// el robot1 termina de transportar el eje y engranaje
					// avisamos a scada
					if(_scada != null)
						_scada.modificarRobot1(MessageTypes.FIN_ROBOT1_TRASLADO_EJE_ENGRANAJE);
					break;
					
				case COMIENZO_ROBOT1_TRASLADO_CONJUNTO:
					// el robot1 comienza a transportar el conjunto
					// avisar a scada
					if(_scada != null) {
						_scada.modificarRobot1(MessageTypes.COMIENZO_ROBOT1_TRASLADO_CONJUNTO);
						_scada.modificarEstacionMontaje(MessageTypes.EXIT);
					}
					break;
					
				case COMIENZO_MONTAJE:
					// avisar a scada
					if(_scada != null)
						_scada.modificarEstacionMontaje(MessageTypes.COMIENZO_MONTAJE);
					break;
					
				case FIN_MONTAJE:
					// avisar a scada
					if(_scada != null)
						_scada.modificarEstacionMontaje(MessageTypes.FIN_MONTAJE);
					break;

				default:
					_logger.finer("UNHANDLED MESSAGE TYPE: "+msj.get_type());
			}
		} else if(_up1)
			_logger.fine("No new messages from automata1");

		return ans;
	}


	/**
	 * Se encarga de procesar los mensajes recibidos del aut&oacute;mata 2 y de ejecutar las acciones pertinentes.
	 * @return true si hab&iacute;a un mensaje y se ha podido procesar correctamente. False en caso contrario.
	 */
	private boolean procesarMensajes2() {
		boolean ans = false;
		Mensaje msj;
		try {
			msj = _buzon2.desencolar();
		} catch (Exception e) {
			_logger.log(Level.SEVERE, "Error al desencolar buzon2", e);
			return ans;
		}

		if(msj != null) {
			_logger.info(msj.get_source()+ " sent " + msj.get_type());

			switch(msj.get_type()) {
				case BEFRIEND:
					befriend(msj.get_message());

					if(_logger.isLoggable(Level.FINER)) {
						String boxes = "";
						for(String str : _slaves.keySet())
							boxes+= "\t"+str;
						_logger.finer("Avaliable mailboxes are:"+boxes);
					}

					// se ha caido
					// enviamos senal de inicio con parametros
					if(_on) {
						Mensaje start = new Mensaje();
						start.set_type(MessageTypes.START);
						start.set_message(_conf);
						try {
							_slaves.get("esclavo2").encolar(start);
						} catch (Exception e) {
							_logger.log(Level.SEVERE,"Esclavo2 is down but it shoudn't be", e);
						}
					}
					break;

				case UNFRIEND: // esto no sera usado
					_slaves.remove(msj.get_source());
					_up2 = false;
					break;

				case ESTADOS2:
					HashMap<String, String> estadoTmp = msj.get_message();
					if(!_estado2.equals(estadoTmp)) {
						_estado2 = estadoTmp;
						// mandarselo al scada
						if(_scada != null)
							_scada.modificarEstadoAutomata2(_estado2);
						_logger.info("Estado2 updated");
					}
					break;

				case CONJUNTO_MONTADO_DISPONIBLE_CINTA_TRANSFERENCIA:
					// conjunto montado disponible para recoger en cinta de transferencia
					// avisar a scada
					if(_scada != null)
						_scada.modificarCintaTransferencia(3);
					
					_transferenciaOut = true;
					break;

				case CONJUNTO_SOLDADO_DISPONIBLE:
					// se ha soldado un conjunto
					// avisar a scada
					if(_scada != null)
						_scada.modificarEstacionSoldadura(MessageTypes.CONJUNTO_SOLDADO_DISPONIBLE);

					_soldaduraIn = false;
					_soldaduraOut = true;
					break;

				case COMIENZO_SOLDADURA:
					// enviar a scada
					if(_scada != null)
						_scada.modificarEstacionSoldadura(MessageTypes.COMIENZO_SOLDADURA);
					break;
					
				case FIN_SOLDADURA:
					// enviar a scada
					if(_scada != null)
						_scada.modificarEstacionSoldadura(MessageTypes.FIN_SOLDADURA);
					break;
					
				default:
					_logger.finer("UNHANDLED MESSAGE TYPE: "+msj.get_type());
			}
		} else if(_up2)
			_logger.fine("No new messages from automata2");

		return ans;
	}

	/**
	 * Se encarga de procesar los mensajes recibidos del aut&oacute;mata 3 y de ejecutar las acciones pertinentes.
	 * @return true si hab&iacute;a un mensaje y se ha podido procesar correctamente. False en caso contrario.
	 */
	private boolean procesarMensajes3() throws Exception {
		boolean ans = false;
		Mensaje msj;
		try {
			msj = _buzon3.desencolar();
		} catch (Exception e) {
			_logger.log(Level.SEVERE, "Error al desencolar buzon3", e);
			return ans;
		}

		if(msj != null) {
			_logger.info(msj.get_source()+ " sent " + msj.get_type());

			switch(msj.get_type()) {
				case BEFRIEND:
					befriend(msj.get_message());

					if(_logger.isLoggable(Level.FINER)) {
						String boxes = "";
						for(String str : _slaves.keySet())
							boxes+= "\t"+str;
						_logger.finer("Avaliable mailboxes are:"+boxes);
					}

					// se ha caido
					// enviamos senal de inicio con parametros
					if(_on) {
						Mensaje start = new Mensaje();
						start.set_type(MessageTypes.START);
						start.set_message(_conf);
						try {
							_slaves.get("esclavo3").encolar(start);
						} catch (Exception e) {
							_logger.log(Level.SEVERE,"Esclavo3 is down but it shoudn't be", e);
						}
					}
					break;

				case UNFRIEND: // esto no sera usado
					_slaves.remove(msj.get_source());
					_up3 = false;
					break;

				case ESTADOS3:
					HashMap<String, String> estadoTmp = msj.get_message();
					if(!_estado3.equals(estadoTmp)) {
						_estado3 = estadoTmp;
						// mandarselo al scada
						if(_scada != null)
							_scada.modificarEstadoAutomata3(_estado3);
						_logger.info("Estado3 updated");
					}
					break;

				case ACCEPTED:
					// se ha producido un conjunto bueno
					// TODO avisar a scada
					//_scada.modificarCintaSalida(estadoActualCintaSalida)
					_scada.incrementarContadorConjuntosAceptados();

					_controlIn = false;
					_controlOut = true;
					_aceptado = true;
					
					break;

				case REJECTED:
					// se ha producido un conjunto malo
					// TODO avisar a scada
					_scada.incrementarContadorConjuntosRechazados();

					_controlIn = false;
					_controlOut = true;
					_aceptado = false;
					
					break;

				default:
					_logger.finer("UNHANDLED MESSAGE TYPE: "+msj.get_type());
			}
		} else if(_up3)
			_logger.fine("No new messages from automata3");

		return ans;
	}

	/**
	 * Da &oacute;rdenes al robot 2 para que coja y deje elementos en las cintas y estaciones a las que tiene
	 * acceso, comprobando que estas est&eacute;n libre o que tengan elementos que coger.
	 */
	private void manejarRobot() {
		if(_robot2.getOrden() == OrdenesRobot2.QUIETO && _up2 && _up3) {
			switch(_orden_anterior) {
				case QUIETO:
					// Este caso solo se da cuando se acaba de arrancar
				case REPOSA:
					// El robot va a la posicion de reposo
					this.intentaCoger();
					break;

				case COGE_MONTADO:
					// robot tiene conjunto montado
					// debe transportalo a estacion soldadura
					_logger.info("Transportando montado a soldador");
					_orden_anterior = OrdenesRobot2.TRANSPORTA_MONTADO_SOLDADOR;
					_robot2.setOrden(_orden_anterior);

					_logger.fine("Orden de robot2: TRANSPORTA_MONTADO_SOLDADOR");
					// avisamos a automata2
					try {
						Mensaje trans = new Mensaje();
						trans.set_type(MessageTypes.CONJUNTO_MONTADO_RETIRADO_CINTA_TRANSFERENCIA);
						_slaves.get("esclavo2").encolar(trans);
						_transferenciaOut = false;
					}catch (Exception e) {
						_logger.log(Level.WARNING, "Esclavo2 is down", e);
						this.automataCaido(2);
					}

					break;

				case COGE_SOLDADO:
					// robot tiene conjunto soldado
					// debe transportarlo a verificador
					_logger.info("Transportando soldado a verificador");
					_orden_anterior = OrdenesRobot2.TRANSPORTA_SOLDADO_VERIFICADOR;
					_robot2.setOrden(_orden_anterior);
					_soldaduraIn = true;
					_soldaduraOut = false;
					
					_logger.fine("Orden de robot2: TRANSPORTA_SOLDADO_VERIFICADOR");

					// avisamos a automata 2
					try {
						Mensaje cogido = new Mensaje();
						cogido.set_type(MessageTypes.CONJUNTO_SOLDADO_RETIRADO_ESTACION_SOLDADURA);
						_slaves.get("esclavo2").encolar(cogido);
						break;
					} catch (RemoteException e) {
						_logger.log(Level.WARNING, "Esclavo2 is down", e);
						this.automataCaido(2);
					}

				case COGE_VERIFICADO:
					// robot tiene conjunto verificado
					// debe transportalo a salida
					if(_aceptado) {
						_logger.info("Transportando aceptado");
						_orden_anterior = OrdenesRobot2.TRANSPORTA_VERFICADO_ACEPTADOS;
					} else {
						_logger.info("Transportando rechazado");
						_orden_anterior = OrdenesRobot2.TRANSPORTA_VERIFICADO_RECHAZADOS;
					}
					
					_logger.fine("Orden de robot2: "+_orden_anterior);

					_robot2.setOrden(_orden_anterior);
					_controlIn = true;
					_controlOut = false;

					break;

				case TRANSPORTA_MONTADO_SOLDADOR:
					// robot ha dejado conjunto montado en estacion de soldadura
					_logger.info("Dejando montado en soldador");
					_soldaduraIn = false;
					_soldaduraOut = false;

					// avisamos a automata 2
					try {
						Mensaje enSoldadura = new Mensaje();
						enSoldadura.set_type(MessageTypes.CONJUNTO_MONTADO_ESTACION_SOLDADURA_ROBOT2);
						_slaves.get("esclavo2").encolar(enSoldadura);
					} catch (Exception e) {
						_logger.log(Level.WARNING, "Esclavo2 is down", e);
						this.automataCaido(2);
					}

					this.intentaCoger();
					break;

				case TRANSPORTA_SOLDADO_VERIFICADOR:
					// robot ha dejado conjunto soldado en estacion de verificacion
					_logger.info("Dejando soldado en verificador");
					_controlIn = false;
					_controlOut = false;

					// avisamos a esclavo 3
					try {
						Mensaje verify = new Mensaje();
						verify.set_type(MessageTypes.INSPECTION);
						_slaves.get("esclavo3").encolar(verify);
					} catch (RemoteException e) {
						_logger.log(Level.WARNING, "Esclavo3 is down", e);
						this.automataCaido(3);
					}

					this.intentaCoger();
					break;


				case TRANSPORTA_VERFICADO_ACEPTADOS:
					// robot ha dejado conjunto verificado en aceptados
					// avisamos a automata 3
					_logger.info("Dejando verificado en aceptados");
					try {
						Mensaje exitWin = new Mensaje();
						exitWin.set_type(MessageTypes.EXIT);
						_slaves.get("esclavo3").encolar(exitWin);
					} catch (Exception e) {
						_logger.log(Level.WARNING, "Esclavo3 is down", e);
						this.automataCaido(3);
					}

					this.intentaCoger();
					break;

				case TRANSPORTA_VERIFICADO_RECHAZADOS:
					// robot ha dejado conjunto verificado en rechazados
					_logger.info("Dejando verificado en rechazados");
					try {
						Mensaje exitFail = new Mensaje();
						exitFail.set_type(MessageTypes.EXIT);
						_slaves.get("esclavo3").encolar(exitFail);
					} catch (Exception e) {
						_logger.log(Level.WARNING, "Esclavo3 is down", e);
						this.automataCaido(3);
					}

					this.intentaCoger();
					break;

				default:
					System.err.println("Unknown order "+_orden_anterior);
			}
		} else
			_logger.fine("robot2 is "+_robot2.getOrden());
	}

	/**
	 * Hace las comprobaciones necesarias para ver si hay elementos que el robot 2 pueda
	 * coger y transladar y en caso de que los haya realiza las acciones necesarias para
	 * que se recojan los objetos.
	 */
	private void intentaCoger() {
		if(_controlOut) {
			_logger.info("Cogiendo verificado");
			_orden_anterior = OrdenesRobot2.COGE_VERIFICADO;
			_robot2.setOrden(_orden_anterior);
			_controlOut = false;
			_scada.modificarEstacionInspeccion(1);
			_logger.fine("Orden de robot2: COGE_VERIFICADO");
		} else if (_soldaduraOut && _controlIn) {
			_logger.info("Cogiendo soldado");
			_orden_anterior = OrdenesRobot2.COGE_SOLDADO; 
			_robot2.setOrden(_orden_anterior);
			_soldaduraOut = false;
			_scada.modificarEstacionSoldadura(MessageTypes.EXIT);
			_logger.fine("Orden de robot2: COGE_SOLDADO");
		} else if(_transferenciaOut && _soldaduraIn) {
			_logger.info("Cogiendo montado");
			_orden_anterior = OrdenesRobot2.COGE_MONTADO; 
			_robot2.setOrden(_orden_anterior);
			_transferenciaOut = false;
			_logger.fine("Orden de robot2: COGE_MONTADO");
		} else {
			if (_orden_anterior != OrdenesRobot2.REPOSA) {
				_orden_anterior = OrdenesRobot2.REPOSA;
				_robot2.setOrden(_orden_anterior);
				_logger.fine("Orden de robot2: REPOSA");
			}
		}
	}

	/**
	 * Se encgarga de obtener el buzon de una aut&oacute;mata para poder establecer la comunicaci&oacute;n.
	 * @param pairs Informaci&oacute;n necesaria para poder obtener el buz&oacute;n. A saber:
	 * ip de la m&aacute;quina (id = ip)
	 * puerto del objeto RMI (id = puerto)
	 * ruta del objeto RMI (id = id) 
	 */
	private void befriend(HashMap<String, String> pairs) {	
		String host = pairs.get("ip");
		int port = Integer.parseInt(pairs.get("puerto"));
		String id = pairs.get("id");

		_logger.fine("Getting mailbox at "+host+":"+port+"/"+id);

		//TODO comprobar si ya estaba registrado antes?

		BuzonIface otro = null;
		int tries = 1;
		while(otro == null) {
			_logger.finer("Retrieval attemps for "+host+":"+port+"/"+id+"\t"+(tries++));
			try {
				otro = (BuzonIface) AgregadorRMI.obtenerRemoto(host, port, id);
				_slaves.put(id, otro);
				_logger.finer("Mailbox retrieved from "+host+":"+port+"/"+id);
			} catch(Exception e){
				otro = null;
				_logger.finer(e.getMessage());
			}
		}

		// actualizamos el estado para saber cuales ya estan
		// notificamos a la interfaz
		if(id.equals("esclavo1")) {
			_up1 = true;
			if(_scada != null)
				_scada.modificarLuzAut1(_up1);
		} else if(id.equals("esclavo2")) {
			_up2 = true;
			if(_scada != null)
				_scada.modificarLuzAut2(_up2);
		} else if(id.equals("esclavo3")) {
			_up3 = true;
			if(_scada != null)
				_scada.modificarLuzAut3(_up3);
		}

		_logger.info(id+" is up");
	}

	/**
	 * Env&iacute;a un mensaje a todos lod aut&oacute;matas. 
	 * @param msj Mensaje a enviar.
	 * @return true si se han enviado correctamente todos los mensajes, false en caso contrario.
	 */
	private boolean bcast(Mensaje msj) {
		boolean ans = false;

		try {
			if(!_slaves.isEmpty()) {
				for(BuzonIface b : _slaves.values()) {
					b.encolar(msj);
				}
				ans = true;
			}
		} catch (RemoteException e) {
			ans = false;
			_logger.log(Level.WARNING, "Error while broadcasting", e);
		}

		return ans;
	}

	/**
	 * Inicializa la ejecuci&oacute;n del aut&oacute;mata maestro para que simimule el comportamiento.
	 */
	@Override
	public void run() {
		_logger.info("Comenzando ejecucion de maestro");
		
		_robot2.start();
		_pos_anterior = _robot2.getPosicion();
		// avisar a SCADA
		if(_scada != null)
			_scada.modificarRobot2(_pos_anterior);

		try {
			// iniciamos el servidor
			_logger.info("Registrando servidores");
			while(!_buzon1.registrar(_port, _id+"1"));
			while(!_buzon2.registrar(_port, _id+"2"));
			while(!_buzon3.registrar(_port, _id+"3"));
			_logger.info("Servidores registrados");

			// esperamos a que esten todos los automatas
			_logger.info("Esperando a automatas");
			while(!(_up1 && _up2 && _up3)) {
				if(!_up1)
					this.procesarMensajes1();
				if(!_up2)
					this.procesarMensajes2();
				if(!_up3)
					this.procesarMensajes3();
			}
			_logger.info("Automatas listos");

			_on = true;

			Mensaje stop = new Mensaje();
			stop.set_type(MessageTypes.STOP);

			Mensaje emergency_stop = new Mensaje();
			emergency_stop.set_type(MessageTypes.EMERGENCY_STOP);

			// bucle de control 
			long elapsed;
			long start_time = System.currentTimeMillis();
			while(_on) {

				synchronized (_parada) {
					if(!_paradaNotified) {
						switch (_parada) {
							case START:
								// obtener parametros de la interfaz
								_conf = Configuracion.getHashMap();
								
								Mensaje start = new Mensaje();
								start.set_type(MessageTypes.START);
								start.set_message(_conf);
								
								this.bcast(start);
								_paradaNotified = true;
								break;
							case STOP:
								this.bcast(stop);
								_paradaNotified = true;
								break;
							case EMERGENCY_STOP:
								this.bcast(emergency_stop);
								_paradaNotified = true;
								break;
							default:
								_logger.fine("not a valid value for _parada "+_parada);
								break;
						}
					}
				}

				this.procesarMensajes1();

				this.procesarMensajes2();

				this.procesarMensajes3();

				this.manejarRobot();
				
				elapsed = System.currentTimeMillis() - start_time; 
				if(elapsed >= _reloj) {
					_logger.finer("_up1 is "+_up1);
					if(_up1) {
						try {
							_slaves.get("esclavo1").isEmpty();
							_logger.info("Esclavo1 is alive");
						} catch (Exception e) {
							_logger.log(Level.WARNING, "Esclavo1 is down", e);
							this.automataCaido(1);
						}
					} else
						_logger.fine("Esclavo1 is down");

					_logger.finer("_up2 is "+_up2);
					if(_up2) {
						try {
							_slaves.get("esclavo2").isEmpty();
							_logger.info("Esclavo2 is alive");
						} catch (Exception e) {
							_logger.log(Level.WARNING, "Esclavo2 is down", e);
							this.automataCaido(2);
						}
					} else
						_logger.fine("Esclavo2 is down");

					_logger.finer("_up3 is "+_up3);
					if(_up3) {
						try {
							_slaves.get("esclavo3").isEmpty();
							_logger.info("Esclavo3 is alive");
						} catch (Exception e) {
							_logger.log(Level.WARNING, "Esclavo3 is down", e);
							this.automataCaido(3);
						}
					} else
						_logger.fine("Esclavo3 is down");
					
					start_time = System.currentTimeMillis();
				} else if(_buzon1.isEmpty() && _buzon2.isEmpty() && _buzon3.isEmpty()) {
					_logger.info("Sleeping for "+(_reloj-elapsed));
					Thread.sleep(_reloj-elapsed);
					if(Thread.interrupted())
						_logger.info("I've been interrupted");
					_logger.info("\n\n\n\n\n\t\t\tNUEVO CICLO");
				}
			}

			this.wait();
		} catch (Exception e) {
			_logger.log(Level.SEVERE,"Error main loop", e);
		}
	}

	/**
	 * Se encarga de realizar los cambio que implican la caida de un aut&oacute;mata.
	 * @param num Indica qu&eacute; aut&oacute;mata se ha caido. Sus valores pueden ser
	 * 1 (esclavo1), 2 (esclavo2), 3 (esclavo3).
	 */
	private void automataCaido(int num) {
		switch(num) {
			case 1:
				if(_up1) {
					_slaves.remove("esclavo1");
					// TODO nos deshacemos de los mensajes?
					try{
						_buzon1.vaciar();
					}catch (Exception e) {
						//_logger.fatal("Buzon1 is missing ", e);
						_logger.log(Level.SEVERE, "buzon1 is missing", e);
					}

					_up1 = false;
					// avisar a SCADA de que 1 esta down
					if(_scada != null)
						_scada.modificarLuzAut1(_up1);
					//_avisado1 = true;
				} else
					_logger.fine("Esclavo1 is already down");
				
				break;
			case 2:
				if(_up2) {
					_slaves.remove("esclavo2");
					// TODO nos deshacemos de los mensajes?
					try {
						_buzon2.vaciar();
					}catch (Exception e) {
						_logger.log(Level.SEVERE, "Buzon2 is missing ", e);
					}

					_up2 = false;
					// avisar a SCADA de que 2 esta down
					if(_scada != null)
						_scada.modificarLuzAut2(_up2);
					//_avisado2 = true;
				} else
					_logger.fine("Esclavo2 is already down");
				
				break;
			case 3:
				if(_up3) {
					_slaves.remove("esclavo3");

					// TODO nos deshacemos de los mensajes?
					try {
						_buzon3.vaciar();
					}catch (Exception e) {
						_logger.log(Level.SEVERE, "Buzon3 is missing ", e);
					}
					_up3 = false;
					// avisar a SCADA de que 3 esta down
					if(_scada != null)
						_scada.modificarLuzAut3(_up3);
					//_avisado3 = true;
				} else
					_logger.fine("Esclavo3 is already down");
				
				break;
			default:
				_logger.fine("Wrong number: "+num);
		}

	}

	/**
	 * Este m&eacute;todo cambia el estado general del sistema.
	 * @param tipo Nuevo estado del sistema. Puede ser STOP, START o EMERGENCY_STOP.
	 */
	public static void parada(MessageTypes tipo) {
		synchronized (_parada) {
			_parada = tipo;
			_paradaNotified = false;
			_logger.info("Mensaje recibido de SCADA: "+tipo);
		}
	}
	
	/**
	 * Cambia la posici&oacute;n del robot 2.
	 * @param pos Nueva posici&oacute;n del robot 2.
	 */
	public static void cambiarPosicionRobot2(PosicionesRobot2 pos) {
		_logger.info("Setting robot2 pos to "+pos);
		if(_scada != null)
			_scada.modificarRobot2(pos);
	}
	
	/**
	 * Inicia la ejecuci&oacute;n del aut&oacute;mata maestro.
	 * @param args no se usan
	 */
	public static void main(String[] args) {
		try {
			Maestro master = new Maestro(null);
			new Thread(master).start();
		} catch (Exception e) {
			e.printStackTrace();
		}
	}
}
